package jmine.tec.hydra.schedule;

import java.text.ParseException;
import java.util.Arrays;
import java.util.HashSet;

import jmine.tec.hydra.domain.specification.HydraScheduledSpecification;

import org.apache.log4j.Logger;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.springframework.beans.factory.InitializingBean;

import bancosys.tec.persist.controller.TransactionalController;
import bancosys.tec.persist.dao.BaseDAO;

/**
 * Responsável por inicializar os Jobs de quartz para todas as especificações agendadas. Garante que não haverá mais de dois Jobs agendados
 * para a mesma especificação.
 * 
 * @author piercio
 * @deprecated use jmine.tec.scheduling
 */
@Deprecated
public class HydraScheduler implements InitializingBean {

    protected static final Logger LOGGER = Logger.getLogger(HydraScheduler.class);

    private TransactionalController controller;

    private Scheduler scheduler;

    private boolean dispatchJobsOnStart;

    /**
     * Cria os Jobs de todas as especificações agendadas.
     */
    public void scheduleJobs() {
        this.controller.startTransaction();
        try {
            BaseDAO<HydraScheduledSpecification> dao = this.controller.getDAOFactory().getGenericDAO(HydraScheduledSpecification.class);
            for (HydraScheduledSpecification spec : dao.findAll()) {
                try {
                    LOGGER.debug("Criando Job " + this.getJobName(spec));
                    this.scheduler.scheduleJob(this.createJob(spec), this.createTrigger(spec));
                } catch (SchedulerException e) {
                    LOGGER.error("Não foi possível agendar a especificação " + spec.getAction() + "para " + spec.getSystem(), e);
                } catch (ParseException e) {
                    LOGGER.error("Cron Expression inválida para " + spec.getAction() + " (" + spec.getCronExpression() + ")", e);
                }
            }
        } finally {
            this.controller.rollback();
        }
    }

    /**
     * Remove todos os jobs associados as especificações agendadas.
     */
    public void deleteJobs() {
        this.controller.startTransaction();
        try {
            BaseDAO<HydraScheduledSpecification> dao = this.controller.getDAOFactory().getGenericDAO(HydraScheduledSpecification.class);
            for (HydraScheduledSpecification spec : dao.findAll()) {
                this.deleteJob(spec);
            }
        } finally {
            this.controller.rollback();
        }
    }

    /**
     * Deletes all the jobs and stops the scheduler
     * 
     * @throws Exception if something goes wrong
     */
    public void shutdownScheduler() throws Exception {
        this.deleteJobs();
        this.scheduler.shutdown();
    }

    /**
     * Cria um Jobs para a especificação agendada passada.
     * 
     * @param spec a especificação agendada
     */
    public void scheduleJob(HydraScheduledSpecification spec) {
        if (!spec.isEnabled()) {
            this.deleteJob(spec);
        }

        try {
            if (this.getJobNames().contains(this.getJobName(spec))) {
                LOGGER.debug("Alterando Job " + spec.getAction() + "para " + spec.getSystem());
                this.scheduler.rescheduleJob(this.getJobName(spec), HydraJob.JOB_GROUP, this.createTrigger(spec));
            } else {
                LOGGER.debug("Criando Job " + spec.getAction() + "para " + spec.getSystem());
                this.scheduler.scheduleJob(this.createJob(spec), this.createTrigger(spec));
            }
        } catch (SchedulerException e) {
            LOGGER.error("Não foi possível agendar a especificação " + spec.getAction() + "para " + spec.getSystem(), e);
        } catch (ParseException e) {
            LOGGER.error("Cron Expression inválida para " + spec.getAction() + " (" + spec.getCronExpression() + ")", e);
        }
    }

    /**
     * Cria um Jobs para a especificação agendada passada.
     * 
     * @param spec a especificação agendada
     */
    public void deleteJob(HydraScheduledSpecification spec) {
        try {
            if (!this.getJobNames().contains(this.getJobName(spec))) {
                return;
            }
            LOGGER.debug("Removendo Job " + spec.getAction() + "para " + spec.getSystem());
            this.scheduler.deleteJob(this.getJobName(spec), HydraJob.JOB_GROUP);
        } catch (SchedulerException e) {
            LOGGER.error("Não foi possível remover o agendamento da especificação " + spec.getAction() + "para " + spec.getSystem(), e);
        }
    }

    /**
     * @return os nomes dos Jobs atualmente agendados.
     */
    private HashSet<String> getJobNames() {
        try {
            return new HashSet<String>(Arrays.asList(this.scheduler.getJobNames(HydraJob.JOB_GROUP)));
        } catch (SchedulerException e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * Define o nome de um job a partir da especificação.
     * 
     * @param spec a especificação.
     * @return o nome do job.
     */
    private String getJobName(HydraScheduledSpecification spec) {
        return spec.getAction() + "_" + spec.getSystem();
    }

    /**
     * Cria um JobDetail do job que deve ser executado para a especificacao agendada.
     * 
     * @param spec a {@link HydraScheduledSpecification}
     * @return o {@link JobDetail}
     */
    public JobDetail createJob(HydraScheduledSpecification spec) {
        JobDetail job = new JobDetail(this.getJobName(spec), HydraJob.JOB_GROUP, HydraJob.class);
        job.getJobDataMap().put(HydraJob.SPECIFICATION_ID, spec.getId());
        job.getJobDataMap().put(HydraJob.CONTROLLER, spec.getController());
        return job;
    }

    /**
     * Cria um Trigger de ativação para a especificação agendada.
     * 
     * @param spec a {@link HydraScheduledSpecification}
     * @return o {@link Trigger}
     * @throws ParseException caso a cron expression não esteja no formato correto.
     */
    public Trigger createTrigger(HydraScheduledSpecification spec) throws ParseException {
        return new CronTrigger(this.getJobName(spec), HydraJob.JOB_GROUP, this.getJobName(spec), HydraJob.JOB_GROUP,
                spec.getCronExpression());
    }

    /**
     * @param controller o valor para o campo controller
     */
    public void setController(TransactionalController controller) {
        this.controller = controller;
    }

    /**
     * @param scheduler o valor para o campo scheduler
     */
    public void setScheduler(Scheduler scheduler) {
        this.scheduler = scheduler;
    }

    /**
     * @param dispatchJobsOnStart se deve ser efetuado o dispatch dos jobs na inicialização do scheduler
     */
    public void setDispatchJobsOnStart(boolean dispatchJobsOnStart) {
        this.dispatchJobsOnStart = dispatchJobsOnStart;
    }

    /**
     * Executa o schedule de jobs apos o spring concluir a criação do bean.
     */
    public void afterPropertiesSet() {
        if (this.dispatchJobsOnStart) {
            this.scheduleJobs();
        }
    }
}